package com.hk.commons.util;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.util.ObjectUtils;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

/**
 * @author kevin
 * @date 2017-11-24 17:00
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class NumberUtils {

    /**
     * 使用位运算，计算两个数之和
     */
    public static int add(int a, int b) {
        int sum = a;
        while (b != 0) {
            sum = a ^ b;
            b = (a & b) << 1;
            a = sum;
        }
        return sum;
    }

    /**
     * 使用位或算，获取一个数的相反数，取反+1表示
     */
    public static int negNum(int value) {
        return add(~value, 1);
    }

    /**
     * 使用位运算，计算两个数之差.
     * a - b 就是 a + b 的相反数，即 a+(-b) ，而 -b 可以用 取反+1表示，即 add(~b,1)
     */
    public static int minus(int a, int b) {
        return add(a, negNum(b));
    }

    /**
     * 使用位运算，计算两个数相乘
     */
    public static int multi(int a, int b) {
        throw new UnsupportedOperationException("unsupported...");
    }

    /**
     * <pre>
     *  比较两个Number是否相等
     *
     *  如果都为null ，返回true,
     *  如果只有任意一个为null ，返回false,
     *  如果参数类型不一样，返回 false,
     *  如果值不一样，返回false
     *  NumberUtils.equals(null,null); return true
     *  NumberUtils.equals(null,1); return false
     *  NumberUtils.equals(1,1L); return false
     *  NumberUtils.equals(1.0,1.0); return true
     *  NumberUtils.equals(1.0,2); return false
     * </pre>
     */
    public static boolean equals(Number n1, Number n2) {
        if (ObjectUtils.nullSafeEquals(n1, n2)) {
            return true;
        }
        if (!n1.getClass().equals(n2.getClass())) {
            return false;
        }
        return n1.equals(n2);
    }

    /**
     * <pre>
     *  数字转换中文大写
     *  example:  1120.12  ->  壹仟壹佰贰拾点壹贰
     * </pre>
     */
    public static String convertToChineseCapitalize(Number value) {
        return convert(value, NumberChinese.CN_UNIT_CAPITALIZE, NumberChinese::getChineseCapitalizeByValue);
    }

    /**
     * <pre>
     * 替换小数点最后面的所有的0，如果去掉后， 最后位为 . 也会去掉
     *
     *  example:
     *      1212.121 ->  1212.121
     *      1212.12100000 ->  1212.121
     *      1212.12100001 ->  1212.12100001
     *      12120000 ->  12120000
     *      12120000.000 ->  12120000
     * </pre>
     *
     * @param value value
     */
    public static String replaceDogEndZero(String value) {
        var str = StringUtils.defaultIfEmpty(value, StringUtils.EMPTY);
        if (StringUtils.contains(str, StringUtils.DOG)) {
            str = str.replaceAll("0+?$", StringUtils.EMPTY);
            if (StringUtils.endsWith(str, StringUtils.DOG)) {
                str = str.substring(0, str.length() - 1);
            }
        }
        return str;
    }

    /**
     * <pre>
     *
     * 数字转换中文小写
     *  example:
     *      1120.12  ->  一千一百二十点一二
     *      11200.12  ->  一万一千二百点一二
     * </pre>
     */

    public static String convertToChinese(Number value) {
        return convert(value, NumberChinese.CN_UNIT, NumberChinese::getChineseByValue);
    }

    private static String convert(Number value, List<String> list, Function<Character, String> function) {
        if (Objects.isNull(value)) {
            return null;
        }
        var strValue = replaceDogEndZero(value.toString());
        var array = StringUtils.tokenizeToStringArray(strValue, String.valueOf(NumberChinese.DOT.getValue()));
        var positive = array[0];
        var decimal = ArrayUtils.length(array) == 2 ? array[1] : null; //小数值
        var sb = new StringBuilder();
        // 整数部分转换
        for (int index = 0, len = positive.length(); index < len; index++) {
            var c = positive.charAt(index);
            if (c == '0') {
                if (positive.charAt(index - 1) != '0') { // 如果上个数也为0，则只写一个0
                    sb.append(function.apply(c));
                }
            } else {
                sb.append(function.apply(c))
                        .append(chineseByPosition(len - index, list));
            }
        }
        if (sb.toString().endsWith(function.apply(NumberChinese.ZERO.getValue()))) {
            sb.deleteCharAt(sb.length() - 1); // 减去最后一个 0，因为整数部分最后的0不会读取出来，如: [11200 -> 一万一千二百] ，而不会转换为 [一万一千二百零]
        }
        // 小数部分转换
        if (StringUtils.isNotEmpty(decimal)) {
            sb.append(function.apply(NumberChinese.DOT.getValue()));
            for (int index = 0, len = decimal.length(); index < len; index++) {
                sb.append(function.apply(decimal.charAt(index)));
            }
        }
        return sb.toString();
    }

    private static String chineseByPosition(int position, List<String> list) {
        if (position <= 1) {
            return StringUtils.EMPTY;
        }
        if (position <= 5) { // 万以内
            return list.get(position - 2);
        }
        if (position <= 8) {
            return list.get(position - 6);
        }
        if (position == 9) { // 亿
            return list.get(4);
        }
        int temp = position % 4;
        return list.get(temp < 2 ? temp + 2 : temp - 2);
    }

    public static boolean equalsAny(Number value, Number... arr) {
        return ArrayUtils.isNotEmpty(arr) &&
                Arrays.stream(arr).anyMatch(item -> equals(item, value));
    }

    /**
     * 比较两个Number是否不相等
     *
     * @param n1 n1
     * @param n2 n2
     * @return true or false
     */
    public static boolean nequals(Number n1, Number n2) {
        return !equals(n1, n2);
    }

    /**
     * 格式化数值为百分比，默认小数位保留2位
     *
     * @param value 值
     * @return format value
     */
    public static String formatPercent(Object value) {
        return formatPercent(value, 2);
    }

    /**
     * 格式化数值为百分比
     *
     * @param value                 值
     * @param maximumFractionDigits 小数位最大位数
     */
    public static String formatPercent(Object value, int maximumFractionDigits) {
        var format = NumberFormat.getPercentInstance();
        format.setMaximumFractionDigits(maximumFractionDigits);
        return format.format(value);
    }

    /**
     * 格式化数值，保留两位小数
     *
     * @param value value
     * @return String
     */
    public static String formatDecimal(Object value) {
        return formatDecimal(value, "#.##");
    }

    /**
     * 格式化数值
     *
     * @param value   value
     * @param pattern pattern 格式化
     * @return format value
     */
    public static String formatDecimal(Object value, String pattern) {
        var format = new DecimalFormat(pattern);
        return format.format(value);
    }

    /**
     * <pre>
     * 千分位保留两位小数
     *
     * example: 189120.12  ->  189,120.12
     * </pre>
     */
    public static String formatThousand(Number value) {
        return formatDecimal(value, ",###.##");
    }

}
